import operator
from itertools import accumulate
from typing import Final

from ng911ok.lib.config_dataclasses import NG911Field, NG911FeatureClass
from ng911ok.lib.iterablenamespace import FrozenList, FrozenDict
from ng911ok.lib.session import config

SET_TO_NULL: Final[str] = "== SET TO NULL =="
"""String to be used as an option in field dropdowns indicating that a field
should not be mapped (and therefore the output be set to null)."""

cf = config.fields  # [c]onfig [f]ields
cfc = config.feature_classes  # [c]onfig [f]eature [c]lasses
esz = cfc.esz_boundary
ems = cfc.esb_ems_boundary
fire = cfc.esb_fire_boundary
law = cfc.esb_law_boundary

esb_feature_classes: FrozenList[NG911FeatureClass] = FrozenList((ems, fire, law))
"""The ESB feature classes. Does **not** include :ng911fc:`esz_boundary`."""

nguid_esz: Final[str] = esz.unique_id.name
dsplayname: Final[str] = cf.dsplayname.name

esn_f: NG911Field = cf.esn
esz_f: NG911Field = cf.esz
ems_f: NG911Field = cf.ems
fire_f: NG911Field = cf.fire
law_f: NG911Field = cf.law
localid_f: NG911Field = cf.local_id
agencyid_f: NG911Field = cf.agency_id

esb_specific_fields: Final[FrozenDict[NG911FeatureClass, NG911Field]] = FrozenDict({ems: ems_f, fire: fire_f, law: law_f})
"""Mapping of ESB feature classes to their corresponding fields in the
:ng911fc:`esz_boundary` feature class."""

always_required_standard_field_names: Final[frozenset[str]] = frozenset(f.name for f in (esz_f, *esb_specific_fields.values()))
"""Names of standard fields for which corresponding source fields are always
required to be provided when dissolving an ESZ feature class."""

_esz_fields: Final[frozenset[NG911Field]] = frozenset(esz.fields.values())
"""Fields in the :ng911fc:`esz_boundary` feature class."""

_all_esb_fields: Final[frozenset[NG911Field]] = frozenset(f for fc in esb_feature_classes for f in fc.fields.values())
"""Union of all fields in the ESB feature classes."""

_common_esb_fields: Final[frozenset[NG911Field]] = [*accumulate([frozenset(fc.fields.values()) for fc in esb_feature_classes], operator.and_)][-1]
"""Set-intersection of all fields in the ESB feature classes, i.e., the fields
common to the ESB feature classes."""

_fields_omit_from_dissolve: Final[frozenset[NG911Field]] = frozenset((cf.dsplayname, cf.local_id))
"""Fields in the :ng911fc:`esz_boundary` feature class that are not relevant to
the dissolve operation and therefore should be omitted from it."""

esz_only_fields: Final[frozenset[NG911Field]] = _esz_fields - _all_esb_fields
"""Fields in :ng911fc:`esz_boundary` that are not present in any of the ESB
feature classes."""

common_esz_esb_fields = _esz_fields & _common_esb_fields
"""Set-intersection of all fields in :ng911fc:`esz_boundary` AND all of the ESB
feature classes, i.e., the fields common to all of those feature classes."""

common_dissolve_fields: Final[frozenset[NG911Field]] = _common_esb_fields - _fields_omit_from_dissolve
"""Fields that are all present in all of the ESB feature classes, not including
those listed for omission. These fields should be used in each dissolve
operation along with the relevant ESB-specific field."""

esb_only_fields: Final[frozenset[NG911Field]] = _common_esb_fields - _esz_fields
"""Fields present in all of the ESB feature classes but not in :ng911fc:`esz_boundary`."""

esz_field_map_parameter_field_names: Final[FrozenList[str]] = FrozenList(sorted(f.name for f in esz_only_fields | _fields_omit_from_dissolve))
"""Standard field names to use in the ESZ field map parameter, sorted by field
name."""

common_field_map_parameter_field_names: Final[FrozenList[str]] = FrozenList(sorted(f.name for f in common_esz_esb_fields - _fields_omit_from_dissolve))
"""Standard field names to use in the common field map parameter, sorted by
field name."""

esb_field_map_parameter_field_names: Final[FrozenList[str]] = FrozenList(sorted(f.name for f in esb_only_fields - _fields_omit_from_dissolve))
"""Standard field names to use in the ESB field map parameter, sorted by field
name."""